package main

import (
	"bufio"
	"bytes"
	_ "embed"
	"fmt"
	"gpiocfg/cfg"
	"regexp"
	"strconv"
	"strings"

	"github.com/atotto/clipboard"
	"github.com/twgh/xcgui/app"
	"github.com/twgh/xcgui/widget"
	"github.com/twgh/xcgui/window"
	"github.com/twgh/xcgui/xcc"
)

//go:embed res/GPIO.zip
var gpioUI []byte

var (
	GPIO_NAME     = []string{"GPIOA", "GPIOB", "GPIOC", "GPIOD", "GPIOE", "GPIOF"}
	GPIO_REG_NAME = []string{"DDR", "ODR", "CR1", "CR2"}
	EXTI_CR_NAME  = []string{"CR1", "CR2"}
)

func main() {
	a := app.New(true)
	a.EnableDPI(true)
	a.EnableAutoDPI(true)
	a.LoadResourceZipMem(gpioUI, "resource.res", "")
	w := window.NewByLayoutZipMem(gpioUI, "main.xml", "", 0, 0)
	body := widget.NewLayoutEleByIDName(w.Handle, "@ID_1")
	//btn := widget.NewButtonByHandle(w.GetChildByID(5))
	arr := make([]*cfg.GpioCfgItem, 0, 100)

	addCfg := func(def bool) *cfg.GpioCfgItem {
		cfg := cfg.AddGpioCfgItemUI(gpioUI, body, def)
		return cfg
	}

	btn := widget.NewButtonByName("load_gpio")
	btn.Event_BnClick(func(pbHandled *bool) int {
		exti, cfg, defv, err := loadFromClipboard()
		if err != nil {
			w.MessageBox("Error", err.Error(), xcc.MessageBox_Flag_Icon_Error, xcc.Window_Style_Default)
			return 0
		}
		for _, vv := range arr[1:] {
			vv.Destroy()
		}
		def := arr[0]
		arr = arr[:1]
		if defv[0] != -1 && defv[1] != -1 && defv[2] != -1 && defv[3] != -1 {
			if defv[0] == 0 {
				def.SetInputConfig(defv[1] != 0, defv[2] != 0, defv[3])
			} else {
				def.SetOutputConfig(defv[1] != 0, defv[2] != 0, defv[3] != 0)
			}
		}
		for gpio, v := range cfg {
			for pin := 0; pin < 8; pin++ {
				// DDR ODR CR1 CR2
				isOutput := v[0]&(1<<pin) != 0
				if isOutput { // input
					isHighLevel := v[1]&(1<<pin) != 0
					isPullPush := v[2]&(1<<pin) != 0
					isHighSpeed := v[3]&(1<<pin) != 0
					if def.IsOutput() && (def.IsHighLevel() == isHighLevel) && (def.IsPullPush() == isPullPush) && (def.IsHighSpeed() == isHighSpeed) {
						continue
					}
					newCfg := addCfg(false)
					newCfg.SetBaseConfig(gpio, pin)
					newCfg.SetOutputConfig(isPullPush, isHighSpeed, isHighLevel)
					arr = append(arr, newCfg)
				} else {
					triggerType := 3 & (exti[pin/4] >> ((pin & 3) * 2))
					isPullUp := v[2]&(1<<pin) != 0
					isInteruptEn := v[3]&(1<<pin) != 0
					if !def.IsOutput() && (!isInteruptEn || def.GetExtiTriggerType() == triggerType) && def.IsPullUp() == isPullUp && def.IsInteruptEn() == isInteruptEn {
						continue
					}
					newCfg := addCfg(false)
					newCfg.SetBaseConfig(gpio, pin)
					newCfg.SetInputConfig(isPullUp, isInteruptEn, triggerType)
					arr = append(arr, newCfg)
				}
			}
		}
		w.Redraw(true)
		return 0
	})

	btn = widget.NewButtonByName("add_gpio")
	btn.Event_BnClick(func(pbHandled *bool) int {
		cfg := addCfg(false)
		last := arr[len(arr)-1]
		if len(arr) > 1 {
			cfg.SetBaseConfig(last.GPIO(), last.Pin())
		}
		if last.IsOutput() {
			cfg.SetOutputConfig(last.IsPullPush(), last.IsHighSpeed(), last.IsHighLevel())
		} else {
			cfg.SetInputConfig(last.IsPullUp(), last.IsInteruptEn(), last.GetExtiTriggerType())
		}
		arr = append(arr, cfg)
		w.Redraw(true)
		return 0
	})

	btn = widget.NewButtonByName("cal_gpio")
	btn.Event_BnClick(func(pbHandled *bool) int {
		makeGpioCfg(arr)
		return 0
	})
	arr = append(arr, cfg.AddGpioCfgItemUI(gpioUI, body, true))
	w.AdjustLayout()
	w.Show(true)
	a.Run()
	a.Exit()
}
func gpioSet(cfg [][]int, gpio int, mask int, v *cfg.GpioCfgItem) {
	if v.IsOutput() {
		cfg[gpio][0] |= mask
		if v.IsHighLevel() {
			cfg[gpio][1] |= mask
		} else {
			cfg[gpio][1] = cfg[gpio][1] &^ mask
		}
		if v.IsPullPush() {
			cfg[gpio][2] |= mask
		} else {
			cfg[gpio][2] = cfg[gpio][2] &^ mask
		}
		if v.IsHighSpeed() {
			cfg[gpio][3] |= mask
		} else {
			cfg[gpio][3] = cfg[gpio][3] &^ mask
		}
	} else {
		cfg[gpio][0] = cfg[gpio][0] &^ mask
		if v.IsPullUp() {
			cfg[gpio][2] |= mask
		} else {
			cfg[gpio][2] = cfg[gpio][2] &^ mask
		}
		if v.IsInteruptEn() {
			cfg[gpio][3] |= mask
		} else {
			cfg[gpio][3] = cfg[gpio][3] &^ mask
		}
	}
}

func extiCfg(exit []int, pin int, cfg *cfg.GpioCfgItem) {
	mask := cfg.GetExtiTriggerType()
	old := exit[pin/4]
	old = old &^ (0x3 << ((pin % 4) * 2))
	old |= mask << ((pin % 4) * 2)
	exit[pin/4] = old
}

func makeGpioCfg(arr []*cfg.GpioCfgItem) {
	// DDR ODR CR1 CR2
	cfg := [][]int{
		{0, 0, 0, 0, 0}, // GPIOA
		{0, 0, 0, 0, 0}, // GPIOB
		{0, 0, 0, 0, 0}, // GPIOC
		{0, 0, 0, 0, 0}, // GPIOD
		{0, 0, 0, 0, 0}, // GPIOE
		{0, 0, 0, 0, 0}, // GPIOF
	}
	exti := []int{0, 0}
	for i, vv := range arr {
		var mask int
		if i == 0 {
			mask = 255
			for gpio := 0; gpio < 6; gpio++ {
				gpioSet(cfg, gpio, mask, vv)
			}
		} else {
			gpio := vv.GPIO()
			if gpio < 0 || gpio > 5 {
				continue
			}
			pin := vv.Pin()
			if pin < 0 || pin > 7 {
				continue
			}
			mask = (1 << pin)
			gpioSet(cfg, gpio, mask, vv)
			if (cfg[gpio][0] & mask) == 0 {
				extiCfg(exti, pin, vv)
			}
		}
	}

	b2i := func(v bool) int {
		if v {
			return 1
		}
		return 0
	}

	var buf bytes.Buffer
	def := arr[0]
	if def.IsOutput() {
		buf.WriteString(fmt.Sprintf("//%v %v %v %v\n", 1, b2i(def.IsPullPush()), b2i(def.IsHighLevel()), b2i(def.IsHighSpeed())))
	} else {
		buf.WriteString(fmt.Sprintf("//%v %v %v %v\n", 0, b2i(def.IsPullUp()), b2i(def.IsInteruptEn()), def.GetExtiTriggerType()))
	}

	for i, item := range cfg {
		for n, v := range item {
			if v != 0 {
				buf.WriteString(fmt.Sprintf("%s->%s = 0x%x;\n", GPIO_NAME[i], GPIO_REG_NAME[n], v))
			}
		}
	}
	for i, item := range exti {
		if item != 0 {
			buf.WriteString(fmt.Sprintf("EXTI->%s = 0x%x;\n", EXTI_CR_NAME[i], item))
		}
	}
	println(buf.String())
	clipboard.WriteAll(buf.String())
}

func find(arr []string, val string) int {
	for i, v := range arr {
		if v == val {
			return i
		}
	}
	return -1
}
func splitLines(input string) []string {
	var lines []string
	scanner := bufio.NewScanner(strings.NewReader(input))
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		fmt.Println("Error:", err)
	}
	return lines
}
func loadFromClipboard() ([]int, [][]int, []int, error) {
	text, err := clipboard.ReadAll()
	if err != nil {
		return nil, nil, nil, err
	}
	// DDR ODR CR1 CR2
	cfg := [][]int{
		{0, 0, 0, 0, 0}, // GPIOA
		{0, 0, 0, 0, 0}, // GPIOB
		{0, 0, 0, 0, 0}, // GPIOC
		{0, 0, 0, 0, 0}, // GPIOD
		{0, 0, 0, 0, 0}, // GPIOE
		{0, 0, 0, 0, 0}, // GPIOF
	}
	exti := []int{0, 0}
	defv := []int{-1, -1, -1, -1}
	patternWithGroups := `(\w+)\s*->\s*(\w+)\s*=\s*((0[x|X][0-9a-fA-F]+)|\d+)\s*;`
	patternWithDef := `//\s*(0[x|X][0-9a-fA-F]+|\d+)\s*(0[x|X][0-9a-fA-F]+|\d+)\s*(0[x|X][0-9a-fA-F]+|\d+)\s*(0[x|X][0-9a-fA-F]+|\d+)\s*`
	reWithDef := regexp.MustCompile(patternWithDef)
	reWithGroups := regexp.MustCompile(patternWithGroups)
	lines := splitLines(text)
	for _, line := range lines {
		v := reWithGroups.FindStringSubmatch(line)
		if v == nil {
			v = reWithDef.FindStringSubmatch(line)
			if v != nil {
				// pass
				val1, err := strconv.ParseInt(v[1], 0, 32)
				if err != nil {
					continue
				}
				defv[0] = int(val1)
				val1, err = strconv.ParseInt(v[2], 0, 32)
				if err != nil {
					continue
				}
				defv[1] = int(val1)
				val1, err = strconv.ParseInt(v[3], 0, 32)
				if err != nil {
					continue
				}
				defv[2] = int(val1)
				val1, err = strconv.ParseInt(v[4], 0, 32)
				if err != nil {
					continue
				}
				defv[3] = int(val1)
			}
			continue
		}
		if v[1] == "EXTI" {
			reg := find(EXTI_CR_NAME, v[2])
			if reg == -1 {
				continue
			}
			val, err := strconv.ParseInt(v[3], 0, 32)
			if err != nil {
				continue
			}
			exti[reg] = int(val)
		} else {
			gpio := find(GPIO_NAME, v[1])
			if gpio == -1 {
				continue
			}
			reg := find(GPIO_REG_NAME, v[2])
			if reg == -1 {
				continue
			}
			val, err := strconv.ParseInt(v[3], 0, 32)
			if err != nil {
				continue
			}
			cfg[gpio][reg] = int(val)
		}
	}
	return exti, cfg, defv, err
}

//var gpioLayout = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
//<head />
//<windowUI center="true" content="炫彩界面库 - 我的窗口名" name="gpio" rect="20,20,576,66" showT="true" windowStyle="0" windowType="frameWindow">
//	<comboBoxUI expandT="true" id="@ID_1" name="drop1" rect="11,33,100,20" showT="true" />
//	<comboBoxUI expandT="true" id="@ID_2" name="drop2" rect="116,33,100,20" showT="true" />
//	<comboBoxUI expandT="true" id="@ID_3" name="drop3" rect="220,33,100,20" showT="true" />
//</windowUI>"
